package com.iakuil.app.common.core;

import cn.dev33.satoken.exception.NotLoginException;
import cn.hutool.core.bean.BeanUtil;
import com.iakuil.app.common.core.annotation.ErrorCode;
import com.iakuil.app.common.core.constant.SysConstant;
import com.iakuil.app.common.core.domain.BaseEntity;
import com.iakuil.app.common.core.domain.PageData;
import com.iakuil.app.common.core.domain.PageQuery;
import com.iakuil.app.common.core.domain.Result;
import com.iakuil.app.common.core.exception.BusinessException;
import com.iakuil.app.common.core.util.*;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.lang.Nullable;

import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * 视图层基类
 *
 * <p>提供统一封装的响应、分页和转换方法。
 * <p>所有RestController强制继承。
 * <p>分页示例：
 * <pre>{@code
 * public Result<Foo> getWithPage() {
 *  startPage();
 *  return pageInfo(fooService.list());
 * }
 * }</pre>
 *
 * <p>BTW. 关于数据校验：
 * <ul>
 * <li>基础数据校验使用JSR 303注解自动完成，通过{@link ErrorCode}注解可以返回指定错误码；
 * <li>Controller层业务逻辑校验结果应该通过{@link #error()}方法尽早返回；
 * <li>Service层的校验错误直接抛出{@link BusinessException}即可。
 * </ul>
 *
 * @author Kai
 */
public abstract class BaseController {

    /**
     * Spring - Conversion Service
     */
    protected final ConversionService conversionService = SpringUtils.getBean(ConversionService.class);

    // ============================= 结果封装 ============================

    /**
     * 操作失败
     *
     * @return 响应结果
     */
    protected <T> Result<T> error() {
        return Result.error();
    }

    /**
     * 操作失败
     *
     * @param code 错误码
     * @return 响应结果
     */
    protected <T> Result<T> error(int code) {
        return Result.error(code);
    }

    /**
     * 操作失败
     *
     * @param msg 返回消息
     * @return 响应结果
     */
    protected <T> Result<T> error(String msg) {
        return Result.error(msg);
    }

    /**
     * 操作失败
     *
     * @param code 错误码
     * @param msg  返回消息
     * @return 响应结果
     */
    protected <T> Result<T> error(int code, String msg) {
        return Result.error(code, msg);
    }

    /**
     * 操作成功
     *
     * @return 统一封装的成功响应
     */
    protected <T> Result<T> success() {
        return Result.success();
    }

    /**
     * 操作成功
     *
     * @param data 返回数据
     * @return 响应结果
     */
    protected <T> Result<T> success(T data) {
        return Result.success(data);
    }

    /**
     * 操作成功
     *
     * @param data  返回数据
     * @param clazz 返回类型
     * @return 响应结果
     */
    protected <T, R> Result<T> success(R data, Class<T> clazz) {
        return Result.success(convert(data, clazz));
    }

    /**
     * 操作成功
     *
     * @param msg 返回消息
     * @return 响应结果
     */
    protected <T> Result<T> success(String msg) {
        return Result.success(msg);
    }

    /**
     * 操作成功
     *
     * @param msg  返回消息
     * @param data 返回数据
     * @return 响应结果
     */
    protected <T> Result<T> success(String msg, T data) {
        return Result.success(msg, data);
    }

    /**
     * 操作成功
     *
     * @param msg   返回消息
     * @param data  返回数据
     * @param clazz 返回类型
     * @return 响应结果
     */
    protected <T, R> Result<T> success(String msg, R data, Class<T> clazz) {
        return Result.success(msg, convert(data, clazz));
    }


    /**
     * 执行操作并返回结果
     *
     * @param expression 操作结果表达式
     * @return 响应结果
     */
    protected Result<Void> success(boolean expression) {
        return expression ? success() : error();
    }

    /**
     * 执行操作并返回结果
     *
     * @param expression 操作结果表达式
     * @param goodNews   成功时响应的消息
     * @param badNews    失败时响应的消息
     * @return 响应结果
     */
    protected Result<Void> success(boolean expression, String goodNews, String badNews) {
        return expression ? success(goodNews) : error(badNews);
    }

    /**
     * 执行操作并返回实体ID
     *
     * <p>一般用于新增场景。
     *
     * @param predicate 创建实体Predicate
     * @param entity    实体对象
     * @return 响应结果
     */
    protected <T extends BaseEntity<?>> Result<Long> success(Predicate<T> predicate, T entity) {
        return success(predicate, entity, null, null);
    }

    /**
     * 执行操作并返回实体ID
     *
     * <p>一般用于新增场景。
     *
     * @param predicate 创建实体Predicate
     * @param entity    实体对象
     * @param goodNews  成功时响应的消息
     * @param badNews   失败时响应的消息
     * @return 响应结果
     */
    protected <T extends BaseEntity<?>> Result<Long> success(Predicate<T> predicate, T entity, String goodNews, String badNews) {
        Long eid = predicate.test(entity) ? entity.getId() : null;
        if (eid != null) {
            return StringUtils.isBlank(goodNews) ? Result.success(eid) : Result.success(goodNews, eid);
        }

        return StringUtils.isBlank(badNews) ? error() : Result.error(badNews);
    }

    // ============================= 分页 ============================

    /**
     * 申明开始分页（Query参数方案）
     *
     * <p>从URL获取参数
     */
    protected void startPage() {
        PageUtils.startPage();
    }

    /**
     * 申明开始分页（Json Body方案）
     *
     * <p>从JSON获取参数
     */
    protected <T extends PageQuery> void startPage(T query) {
        String sort = query.getSort().stream().map(item -> StringUtils.toUnderlineCase(item.toString())).collect(Collectors.joining(","));
        PageUtils.startPage(query.getPageNum(), query.getPageSize(), sort);
    }

    /**
     * 清除分页
     */
    protected void clearPage() {
        PageUtils.clearPage();
    }

    /**
     * 返回分页
     *
     * @param data 分页对象
     * @return 响应结果
     */
    protected <T> PageData<T> pageInfo(List<T> data) {
        return new PageData<>(data);
    }

    // ============================= 对象转换 ============================

    /**
     * 类型转换
     *
     * @param source     源对象
     * @param targetType 目标类型
     * @return 目标类型对象
     */
    protected <T, R> T convert(@Nullable R source, Class<T> targetType) {
        return convert(source, targetType, false);
    }

    /**
     * 类型转换
     *
     * @param source     源对象
     * @param targetType 目标类型
     * @param throwEx    是否抛出异常（源对象为空，或转换后的对象所有属性全部为空）
     * @return 目标类型对象
     */
    @Nullable
    protected <T, R> T convert(@Nullable R source, Class<T> targetType, boolean throwEx) {
        if (source == null) {
            if (throwEx) {
                throw new IllegalArgumentException("Source must not be null");
            } else {
                return null;
            }
        }

        // 如果没有配置转换器则使用BeanUtils兜底
        T result = conversionService.canConvert(source.getClass(), targetType) ? conversionService.convert(source, targetType) : BeanUtils.copy(source, targetType);

        if (throwEx && BeanUtil.isEmpty(result, "ts")) {
            // 转换结果属性全部为空
            throw new IllegalArgumentException("Result object is empty");
        }
        return result;
    }

    /**
     * 批量类型转换
     *
     * @param sourceList 源对象列表
     * @param targetType 目标类型
     * @return 目标类型对象列表
     */
    @SuppressWarnings("unchecked")
    protected <T> List<T> convertAll(List<?> sourceList, Class<T> targetType) {
        if (CollectionUtils.isEmpty(sourceList)) {
            return Collections.emptyList();
        }
        return conversionService.canConvert(sourceList.get(0).getClass(), targetType)
            ? (List<T>) conversionService.convert(sourceList, TypeDescriptor.forObject(sourceList), TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(targetType)))
            : BeanUtils.copyMany(sourceList, targetType);
    }

    // ============================= Security ============================

    /**
     * 获取当前登录用户ID
     *
     * <p>如果不存在，返回默认值（一般是数字0）
     *
     * @return 用户ID
     */
    protected Long getCurrentUserId() {
        return getCurrentUserId(false);
    }

    /**
     * 获取当前登录用户ID
     *
     * <p>如果不存在，可以选择抛出异常，或者返回默认值
     * <p>实际生产环境，可以直接缓存UserInfo
     *
     * @param throwEx 是否抛出异常
     * @return 用户ID
     */
    protected Long getCurrentUserId(boolean throwEx) {
        Long uid = SecurityUtils.getCurrentUserId();
        if (uid == null && throwEx) {
            throw new NotLoginException(NotLoginException.NOT_TOKEN_MESSAGE, "login", NotLoginException.NOT_TOKEN);
        }
        return ObjectUtils.defaultIfNull(uid, SysConstant.SYS_USER_ID);
    }
}
